|
1
|
|
|
/* |
|
2
|
|
|
* decimal.js-i18n v0.2.6 |
|
3
|
|
|
* Full internationalization support for decimal.js. |
|
4
|
|
|
* MIT License |
|
5
|
|
|
* Copyright (c) 2022 Pedro José Batista <[email protected]> |
|
6
|
|
|
* https://github.com/pjbatista/decimal.js-i18n |
|
7
|
|
|
*/ |
|
8
|
1 |
|
import Decimal from "decimal.js"; |
|
9
|
|
|
import type BaseFormatOptions from "./baseOptions"; |
|
10
|
|
|
import type FormatCompactDisplay from "./compactDisplay"; |
|
11
|
1 |
|
import { BIGINT_MODIFIERS, ECMA_LIMIT, LOCALES, PLAIN_MODIFIERS } from "./constants"; |
|
12
|
|
|
import type FormatCurrency from "./currency"; |
|
13
|
|
|
import type FormatCurrencyDisplay from "./currencyDisplay"; |
|
14
|
|
|
import type FormatCurrencySign from "./currencySign"; |
|
15
|
|
|
import type FormatLocale from "./locale"; |
|
16
|
|
|
import type FormatLocaleMatcher from "./localeMatcher"; |
|
17
|
|
|
import type FormatNotation from "./notation"; |
|
18
|
|
|
import type FormatNumberingSystem from "./numberingSystem"; |
|
19
|
|
|
import type FormatOptions from "./options"; |
|
20
|
1 |
|
import { extend, resolve, toEcma, validate } from "./options"; |
|
21
|
|
|
import type FormatPart from "./part"; |
|
22
|
1 |
|
import { exponents, fractions, integerGroups, integers, PartValue } from "./part"; |
|
23
|
|
|
import type FormatPartTypes from "./partTypes"; |
|
24
|
|
|
import type ResolvedFormatOptions from "./resolvedFormatOptions"; |
|
25
|
|
|
import type FormatSignDisplay from "./signDisplay"; |
|
26
|
|
|
import type FormatStyle from "./style"; |
|
27
|
|
|
import type FormatTrailingZeroDisplay from "./trailingZeroDisplay"; |
|
28
|
|
|
import type FormatUnit from "./unit"; |
|
29
|
|
|
import type FormatUnitDisplay from "./unitDisplay"; |
|
30
|
|
|
import type FormatUseGrouping from "./useGrouping"; |
|
31
|
|
|
|
|
32
|
|
|
// Calculates an exponential value using base₁₀ |
|
33
|
1 |
|
const defaultLocales = LOCALES.slice(); |
|
34
|
|
|
|
|
35
|
1 |
|
const concatenate = <T extends PartValue>(filter: T[] | ((p: T) => boolean), parts: T[] = []) => { |
|
36
|
82582 |
|
if (typeof filter === "function") { |
|
37
|
82568 |
|
parts = parts.filter(filter); |
|
38
|
|
|
} else { |
|
39
|
14 |
|
parts = filter; |
|
40
|
|
|
} |
|
41
|
|
|
|
|
42
|
82582 |
|
return parts.map(p => p.value).join(""); |
|
43
|
|
|
}; |
|
44
|
|
|
|
|
45
|
5520 |
|
const pow10 = (exponent: Decimal.Value) => Decimal.pow(10, exponent); |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* The `Decimal.Format` object enables language-sensitive decimal number formatting. It is entirely based on |
|
49
|
|
|
* `Intl.NumberFormat`, with the options of the latter being 100% compatible with it. |
|
50
|
|
|
* |
|
51
|
|
|
* This class, however, extend the numeric digits constraints of `Intl.NumberFormat` from 21 to 1000000000 in |
|
52
|
|
|
* order to fully take advantage of the arbitrary-precision of `decimal.js`. |
|
53
|
|
|
* |
|
54
|
|
|
* @template TNotation Numeric notation of formatting. |
|
55
|
|
|
* @template TStyle Numeric style of formatting. |
|
56
|
|
|
*/ |
|
57
|
1 |
|
export class Format<TNotation extends FormatNotation = "standard", TStyle extends FormatStyle = "decimal"> { |
|
58
|
1 |
|
static readonly [Symbol.toPrimitive] = Format; |
|
59
|
1169 |
|
readonly [Symbol.toStringTag] = "Decimal.Format"; |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* Formats a number according to the locale and formatting options of this {@link Format} object. |
|
63
|
|
|
* |
|
64
|
|
|
* @param value A valid [Decimal.Value](https://mikemcl.github.io/decimal.js/#decimal) to format. |
|
65
|
|
|
* @returns Formatted localized string. |
|
66
|
|
|
*/ |
|
67
|
|
|
readonly format: (value: Decimal.Value) => string; |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* Allows locale-aware formatting of strings produced by `Decimal.Format` formatters. |
|
71
|
|
|
* |
|
72
|
|
|
* @param value A valid [Decimal.Value](https://mikemcl.github.io/decimal.js/#decimal) to format. |
|
73
|
|
|
* @returns An array of objects containing the formatted number in parts. |
|
74
|
|
|
*/ |
|
75
|
|
|
readonly formatToParts: (value: Decimal.Value) => FormatPart[]; |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* Returns a new object with properties reflecting the locale and number formatting options computed during |
|
79
|
|
|
* initialization of this {@link Decimal.Format} object. |
|
80
|
|
|
* |
|
81
|
|
|
* @returns A new object with properties reflecting the locale and number formatting options computed |
|
82
|
|
|
* during the initialization of this object. |
|
83
|
|
|
*/ |
|
84
|
|
|
readonly resolvedOptions: () => ResolvedFormatOptions<TNotation, TStyle>; |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Creates a new instance of the `Decimal.Format` object. |
|
88
|
|
|
* |
|
89
|
|
|
* @param locales A string with a [BCP 47](https://www.rfc-editor.org/info/bcp47) language tag, or an array |
|
90
|
|
|
* of such strings. |
|
91
|
|
|
* |
|
92
|
|
|
* For the general form and interpretation of this parameter, see the [Intl page on |
|
93
|
|
|
* MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl). |
|
94
|
|
|
* @param options Object used to configure the behavior of the string localization. |
|
95
|
|
|
* @throws `RangeError` when an invalid option is given. |
|
96
|
|
|
*/ |
|
97
|
|
|
constructor(locales?: FormatLocale | FormatLocale[], options?: FormatOptions<TNotation, TStyle>) { |
|
98
|
1169 |
|
options ??= {}; |
|
99
|
|
|
|
|
100
|
|
|
// 1. Check if options do not extrapolate the limits of decimal.js |
|
101
|
1169 |
|
const valid = validate(options); |
|
102
|
|
|
|
|
103
|
1169 |
|
if (valid !== true) { |
|
104
|
|
|
// -> it will either be exactly true or contain an array with all faulty properties: |
|
105
|
5 |
|
throw new RangeError(`${valid.join()} value${valid.length === 1 ? " is" : "s are"} out of range."`); |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
// 2. Create a baseline native formatter native |
|
109
|
1164 |
|
const ecmaOptions = toEcma(options); |
|
110
|
1164 |
|
const ecmaFormat = new Intl.NumberFormat(locales, ecmaOptions); |
|
111
|
|
|
|
|
112
|
|
|
// 3. Resolve this object's options, using the native resolution as a baseline |
|
113
|
1164 |
|
const resolved = resolve(options, ecmaFormat.resolvedOptions()); |
|
114
|
1164 |
|
const { minimumIntegerDigits: minID, notation, rounding, style } = resolved; |
|
115
|
|
|
|
|
116
|
|
|
// 4. Create two auxiliary formatters: |
|
117
|
|
|
// One for the integer part, which can have up to a billion minimum digits... |
|
118
|
1164 |
|
const bigintOptions = extend(ecmaOptions, BIGINT_MODIFIERS); |
|
119
|
1164 |
|
const bigintFormat = new Intl.NumberFormat(locales, bigintOptions); |
|
120
|
|
|
|
|
121
|
|
|
// ...and another for a plain, localized reference, used for decimals and constants |
|
122
|
1164 |
|
const plainOptions = extend(bigintOptions, PLAIN_MODIFIERS); |
|
123
|
1164 |
|
const plainFormat = new Intl.NumberFormat(locales, plainOptions); |
|
124
|
|
|
|
|
125
|
|
|
// 5. Localized numeric constants |
|
126
|
1164 |
|
const numbers = Array(10) |
|
127
|
|
|
.fill(null) |
|
128
|
11640 |
|
.map((_, index) => plainFormat.format(index)); |
|
129
|
1164 |
|
const numberMatch = new RegExp("[" + numbers.join("") + "]", "g"); |
|
130
|
1164 |
|
const minusSign = /−/gu; |
|
131
|
|
|
|
|
132
|
|
|
// 5.1. Localized zero and one used in substitutions |
|
133
|
1164 |
|
const [zero, one] = numbers; |
|
134
|
|
|
|
|
135
|
|
|
// 5.2. Helper functions |
|
136
|
4590 |
|
const indexOfValue = (value: string) => numbers.indexOf(value).toString(); |
|
137
|
4584 |
|
const convert = (text: string) => text.replaceAll(numberMatch, indexOfValue).replaceAll(minusSign, "-"); |
|
138
|
1164 |
|
const zeroFill = (size: number) => Array(size).fill(zero).join(""); |
|
139
|
1164 |
|
const zeroTrim = (text: string) => { |
|
140
|
20642 |
|
let result = text; |
|
141
|
|
|
|
|
142
|
20642 |
|
while (result[0] === zero && result.length > 1) { |
|
143
|
335 |
|
result = result.substring(1); |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
20642 |
|
return result; |
|
147
|
|
|
}; |
|
148
|
|
|
|
|
149
|
|
|
// #region Step 6. Main format method - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
150
|
1164 |
|
const _formatToParts = (value: Decimal.Value) => { |
|
151
|
25226 |
|
value = new Decimal(value); |
|
152
|
25226 |
|
const sign = value.s; |
|
153
|
|
|
|
|
154
|
|
|
// 6.1. Create a baseline part array |
|
155
|
25226 |
|
const ecmaParts = ecmaFormat.formatToParts(value.toNumber()); |
|
156
|
|
|
|
|
157
|
|
|
// -> if the value is non-numeric or an infinity, the baseline is good enough |
|
158
|
25226 |
|
if ((value.isFinite && !value.isFinite()) || (value.isNaN && value.isNaN())) { |
|
159
|
4584 |
|
return ecmaParts; |
|
160
|
|
|
} |
|
161
|
|
|
|
|
162
|
|
|
// 6.2. Splitting the parts for easier assembly |
|
163
|
20642 |
|
const ecmaExponentValue = concatenate(exponents, ecmaParts) || "0"; |
|
164
|
20642 |
|
const ecmaIntegerParts = ecmaParts.filter(integerGroups); |
|
165
|
20642 |
|
const ecmaIntegerTrimmed = zeroTrim(concatenate(integers, ecmaIntegerParts)); |
|
166
|
20642 |
|
const ecmaIntegerDigits = concatenate(integers, ecmaIntegerParts).length; |
|
167
|
20642 |
|
const ecmaIntegerTrimmedDigits = ecmaIntegerTrimmed.length; |
|
168
|
20642 |
|
const ecmaFractionValue = concatenate(fractions, ecmaParts); |
|
169
|
20642 |
|
const ecmaFractionDigits = ecmaFractionValue.length; |
|
170
|
|
|
|
|
171
|
|
|
// 6.3. Shifting exponents according to notation/style |
|
172
|
|
|
|
|
173
|
|
|
// 6.3.1. Compact notation: calculate the shift in integer digits, and therefore exponent |
|
174
|
20642 |
|
if (notation === "compact" && !value.eq(0)) { |
|
175
|
3664 |
|
const baseInteger = value.abs().trunc().toFixed(); |
|
176
|
3664 |
|
const baseIntegerDigits = baseInteger.length; |
|
177
|
3664 |
|
const correctionDigits = baseIntegerDigits - ecmaIntegerTrimmedDigits; |
|
178
|
|
|
|
|
179
|
3664 |
|
if (correctionDigits > 0) { |
|
180
|
888 |
|
value = value.mul(pow10(-correctionDigits)); |
|
181
|
|
|
} |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
// 6.3.2. Engr./Scientific notations: evaluate the exponent from the text |
|
185
|
20642 |
|
if ((notation === "engineering" || notation === "scientific") && ecmaExponentValue !== zero) { |
|
186
|
4584 |
|
const exponential = new Decimal(convert(ecmaExponentValue)); |
|
187
|
4584 |
|
value = value.mul(pow10(exponential.mul(-1))).abs().mul(sign); // prettier-ignore |
|
188
|
|
|
} |
|
189
|
|
|
|
|
190
|
|
|
// 6.3.3. Percent style: shift the value accordingly (non numeric parts will remain the same) |
|
191
|
20642 |
|
if (style === "percent") value = value.mul(100); |
|
192
|
|
|
|
|
193
|
|
|
// 6.4. Parsing the information about the numeric parts |
|
194
|
20642 |
|
const integer = value.abs().trunc().mul(sign); |
|
195
|
20642 |
|
const fraction = value.sub(integer).abs(); |
|
196
|
20642 |
|
const integerDigits = !value.eq(0) && integer.eq(0) ? 0 : value.abs().trunc().toFixed().length; |
|
197
|
20642 |
|
const fractionDigits = value.dp(); |
|
198
|
20642 |
|
const maxSD = resolved.maximumSignificantDigits ?? resolved.maximumFractionDigits! + integerDigits; |
|
199
|
20642 |
|
const maxFD = resolved.maximumFractionDigits ?? maxSD - integerDigits; |
|
200
|
20642 |
|
const minSD = resolved.minimumSignificantDigits ?? resolved.minimumFractionDigits! + integerDigits; |
|
201
|
20642 |
|
const minFD = resolved.minimumFractionDigits ?? minSD - integerDigits; |
|
202
|
|
|
|
|
203
|
|
|
// 6.5. Check for the possibility of the native formatter to have accomplished the desired output |
|
204
|
20642 |
|
const integerCheck = !ecmaIntegerParts.length || (minID <= ECMA_LIMIT && ecmaIntegerDigits >= minID); |
|
205
|
20642 |
|
const fractionCheck = !ecmaFractionDigits || (minFD < ECMA_LIMIT && ecmaFractionDigits >= minFD); |
|
206
|
|
|
|
|
207
|
|
|
// -> if the native formatter is good enough for our decimal value, leave it as-is |
|
208
|
20642 |
|
if (integerCheck && fractionCheck) { |
|
209
|
20612 |
|
return ecmaParts as FormatPart[]; |
|
210
|
|
|
} |
|
211
|
|
|
|
|
212
|
|
|
// 6.6. Create the integer value |
|
213
|
30 |
|
const integerParts = (() => { |
|
214
|
30 |
|
if (integerCheck) return ecmaIntegerParts; |
|
215
|
|
|
|
|
216
|
|
|
// Expanding the integer part |
|
217
|
19 |
|
const targetDigits = Math.max(integerDigits, minID); |
|
218
|
|
|
|
|
219
|
|
|
// Creates a base 10 power of the target digits |
|
220
|
19 |
|
const bigint = BigInt(pow10(targetDigits - 1).toFixed()); |
|
221
|
|
|
|
|
222
|
|
|
// Format using the bigint formatter and cut it before joining with the ECMA parts |
|
223
|
19 |
|
const bigintIntegerParts = bigintFormat.formatToParts(bigint).filter(integerGroups); |
|
224
|
|
|
|
|
225
|
|
|
// We need to replace the first 'one' (from the base 10 power) with a 'zero' |
|
226
|
19 |
|
bigintIntegerParts[0].value = bigintIntegerParts[0].value.replace(new RegExp(one), zero); |
|
227
|
|
|
|
|
228
|
|
|
// Merge the first part with the bigint part |
|
229
|
19 |
|
ecmaIntegerParts[0].value = |
|
230
|
|
|
bigintIntegerParts[ecmaIntegerParts.length - 1].value.slice(0, -ecmaIntegerParts[0].value.length) + |
|
231
|
|
|
ecmaIntegerParts[0].value; |
|
232
|
|
|
|
|
233
|
19 |
|
return [...bigintIntegerParts.slice(0, -ecmaIntegerParts.length), ...ecmaIntegerParts]; |
|
234
|
|
|
})(); |
|
235
|
|
|
|
|
236
|
|
|
// 6.7. Create the fraction value |
|
237
|
30 |
|
const fractionValue = (() => { |
|
238
|
30 |
|
if (fractionCheck) return ecmaFractionValue; |
|
239
|
|
|
|
|
240
|
|
|
// Simpler formatting if there is actually no fraction |
|
241
|
29 |
|
if (fraction.eq(0)) { |
|
242
|
13 |
|
return plainFormat.format(BigInt(pow10(minFD).toFixed())).slice(1); |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
16 |
|
let suffix = ""; |
|
246
|
|
|
|
|
247
|
16 |
|
const targetDigits = maxFD - 1; |
|
248
|
|
|
// Exponential value of the fraction (converting from decimal to bigint) |
|
249
|
16 |
|
const exponential = fraction.toDP(targetDigits, rounding).mul(pow10(targetDigits)).toFixed(); |
|
250
|
|
|
|
|
251
|
|
|
// First, create a zero-filled right-side expansion if the digits are insufficient |
|
252
|
16 |
|
if (fractionDigits < minFD) { |
|
253
|
15 |
|
suffix = zeroFill(minFD - targetDigits); |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
16 |
|
const fractionValue = plainFormat.format(BigInt(exponential)) + suffix; |
|
257
|
|
|
|
|
258
|
|
|
// If the value is still not enough, it needs more left-zero-filling |
|
259
|
16 |
|
if (fractionValue.length < minFD) { |
|
260
|
3 |
|
return zeroFill(minFD - fractionValue.length) + fractionValue; |
|
261
|
|
|
} |
|
262
|
|
|
|
|
263
|
13 |
|
return fractionValue; |
|
264
|
|
|
})(); |
|
265
|
|
|
|
|
266
|
|
|
// 6.8. Parsing the numeric fragments in a unified part array |
|
267
|
30 |
|
const result: FormatPart[] = []; |
|
268
|
30 |
|
let integerDone = false; |
|
269
|
30 |
|
let fractionDone = false; |
|
270
|
|
|
|
|
271
|
30 |
|
while (ecmaParts.length) { |
|
272
|
383 |
|
const { type, value } = ecmaParts.shift()!; |
|
273
|
|
|
|
|
274
|
383 |
|
if (type === "integer" || type === "group") { |
|
275
|
280 |
|
if (!integerDone) { |
|
276
|
30 |
|
integerDone = true; |
|
277
|
30 |
|
result.push(...integerParts); |
|
278
|
|
|
} |
|
279
|
280 |
|
continue; |
|
280
|
|
|
} |
|
281
|
|
|
|
|
282
|
103 |
|
if (type === "fraction") { |
|
283
|
29 |
|
if (!fractionDone) { |
|
284
|
29 |
|
fractionDone = true; |
|
285
|
29 |
|
result.push({ type, value: fractionValue }); |
|
286
|
|
|
} |
|
287
|
29 |
|
continue; |
|
288
|
|
|
} |
|
289
|
|
|
|
|
290
|
74 |
|
result.push({ type, value }); |
|
291
|
|
|
} |
|
292
|
30 |
|
return result; |
|
293
|
|
|
}; |
|
294
|
|
|
//#endregion |
|
295
|
|
|
|
|
296
|
1164 |
|
this.format = value => concatenate(_formatToParts(value)); |
|
297
|
25212 |
|
this.formatToParts = value => _formatToParts(value); |
|
298
|
1164 |
|
this.resolvedOptions = () => ({ ...resolved }); |
|
299
|
|
|
} |
|
300
|
|
|
|
|
301
|
|
|
/** |
|
302
|
|
|
* Returns an array containing the default locales available to the environment, based on a default |
|
303
|
|
|
* dictionary of locales and regions. |
|
304
|
|
|
* |
|
305
|
|
|
* **Note:** This method is non-standard and not available on `Intl` formatters. |
|
306
|
|
|
* |
|
307
|
|
|
* @returns Array of strings with the available locales. |
|
308
|
|
|
*/ |
|
309
|
|
|
static supportedLocales(): FormatLocale[] { |
|
310
|
2 |
|
return Intl.NumberFormat.supportedLocalesOf(defaultLocales); |
|
311
|
|
|
} |
|
312
|
|
|
|
|
313
|
|
|
/** |
|
314
|
|
|
* Returns an array containing those of the provided locales that are supported without having to fall back |
|
315
|
|
|
* to the runtime's default locale. |
|
316
|
|
|
* |
|
317
|
|
|
* @template TNotation Numeric notation of formatting. |
|
318
|
|
|
* @template TStyle Numeric style of formatting. |
|
319
|
|
|
* @param locales A string with a BCP 47 language tag, or an array of such strings. For the general form |
|
320
|
|
|
* and interpretation of the locales argument, see the [Intl page on |
|
321
|
|
|
* MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl). |
|
322
|
|
|
* @param options Object used to configure the behavior of the string localization. |
|
323
|
|
|
* @returns Array of strings with the available locales. |
|
324
|
|
|
*/ |
|
325
|
|
|
static supportedLocalesOf<TNotation extends FormatNotation = "standard", TStyle extends FormatStyle = "decimal">( |
|
326
|
|
|
locales: string | string[], |
|
327
|
|
|
options?: FormatOptions<TNotation, TStyle>, |
|
328
|
|
|
) { |
|
329
|
2 |
|
return Intl.NumberFormat.supportedLocalesOf(locales, options ? toEcma(options) : undefined) as FormatLocale[]; |
|
330
|
|
|
} |
|
331
|
|
|
} |
|
332
|
|
|
// eslint-disable-next-line @typescript-eslint/no-namespace |
|
333
|
|
|
export declare namespace Format { |
|
334
|
|
|
export type { |
|
335
|
|
|
BaseFormatOptions, |
|
336
|
|
|
FormatCompactDisplay, |
|
337
|
|
|
FormatCurrency, |
|
338
|
|
|
FormatCurrencyDisplay, |
|
339
|
|
|
FormatCurrencySign, |
|
340
|
|
|
FormatLocale, |
|
341
|
|
|
FormatLocaleMatcher, |
|
342
|
|
|
FormatNotation, |
|
343
|
|
|
FormatNumberingSystem, |
|
344
|
|
|
FormatOptions, |
|
345
|
|
|
FormatPart, |
|
346
|
|
|
FormatPartTypes, |
|
347
|
|
|
ResolvedFormatOptions, |
|
348
|
|
|
FormatSignDisplay, |
|
349
|
|
|
FormatStyle, |
|
350
|
|
|
FormatTrailingZeroDisplay, |
|
351
|
|
|
FormatUnit, |
|
352
|
|
|
FormatUnitDisplay, |
|
353
|
|
|
FormatUseGrouping, |
|
354
|
|
|
}; |
|
355
|
|
|
} |
|
356
|
|
|
export type { |
|
357
|
|
|
BaseFormatOptions, |
|
358
|
|
|
FormatCompactDisplay, |
|
359
|
|
|
FormatCurrency, |
|
360
|
|
|
FormatCurrencyDisplay, |
|
361
|
|
|
FormatCurrencySign, |
|
362
|
|
|
FormatLocale, |
|
363
|
|
|
FormatLocaleMatcher, |
|
364
|
|
|
FormatNotation, |
|
365
|
|
|
FormatNumberingSystem, |
|
366
|
|
|
FormatOptions, |
|
367
|
|
|
FormatPart, |
|
368
|
|
|
FormatPartTypes, |
|
369
|
|
|
ResolvedFormatOptions, |
|
370
|
|
|
FormatSignDisplay, |
|
371
|
|
|
FormatStyle, |
|
372
|
|
|
FormatTrailingZeroDisplay, |
|
373
|
|
|
FormatUnit, |
|
374
|
|
|
FormatUnitDisplay, |
|
375
|
|
|
FormatUseGrouping, |
|
376
|
|
|
}; |
|
377
|
|
|
|
|
378
|
|
|
export default Format; |
|
379
|
|
|
|